﻿using System;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Text.RegularExpressions;
using log4net;
using Util;

namespace Updater.DatabaseUpdater
{
    /// <summary>
    /// Used for database update.
    /// </summary>
    public class DatabaseUpdater
    {
        #region constants

        /// <summary>
        /// Sql script file extension.
        /// </summary>
        private const string ScriptExtension = ".sql";

        #endregion

        #region callbacks

        /// <summary>
        /// Function get the current version of the database.
        /// </summary>
        public Func<Version> GetDatabaseVersion;

        /// <summary>
        /// Action set the current version of the database.
        /// </summary>
        public Action<Version> SetDatabaseVersion;

        #endregion

        #region fields

        private string folderPath;

        /// <summary>
        /// Logger.
        /// </summary>
        private ILog m_Log = LogManager.GetLogger(typeof (DatabaseUpdater));

        #endregion

        #region properties

        /// <summary>
        /// Connection string to the database.
        /// </summary>
        private string ConnectionString { get; set; }

        /// <summary>
        /// Sql script file name.
        /// </summary>
        private string ScriptFileName { get; set; }

        /// <summary>
        /// Folder with sql script file.
        /// </summary>
        private string FolderPath
        {
            get { return folderPath; }
            set
            {
                if (string.IsNullOrEmpty(value))
                    folderPath = Directory.GetCurrentDirectory();
                else
                    folderPath = value.TrimEnd('\\');
            }
        }

        #endregion

        #region constructor

        /// <summary>
        /// Create new instance of <see cref="DatabaseUpdater"/>.
        /// </summary>
        /// <param name="connectionString">The connection string to updated database.</param>
        /// <param name="scriptFileName">The name of sql script file.</param>
        public DatabaseUpdater(string connectionString, string scriptFileName) 
            : this(connectionString, scriptFileName, string.Empty)
        { }

        /// <summary>
        /// Create new instance of <see cref="DatabaseUpdater"/>.
        /// </summary>
        /// <param name="connectionString">The connection string to updated database.</param>
        /// <param name="scriptFileName">The name of sql script file.</param>
        /// <param name="folderPath">The folder with script file.</param>
        public DatabaseUpdater(string connectionString, string scriptFileName,  string folderPath)
        {
            ConnectionString = connectionString;
            ScriptFileName = scriptFileName;
            FolderPath = folderPath;
        }

        #endregion

        #region public methods

        /// <summary>
        /// Execute new sql script if find.
        /// </summary>
        public void UpdateDatabase()
        {
            Version currentVersion = InvokeGetDatabaseVersion();
            if(currentVersion != null)
            {
                Version newVersion = null;
                if (Directory.Exists(FolderPath))
                {
                    foreach (string filename in Directory.GetFiles(FolderPath))
                    {
                        var matchVersion = Regex.Match(filename, @"^.*\\(.*?)\.");
                        if (matchVersion.Success && matchVersion.Groups[1].Value == ScriptFileName)
                        {
                            var fileVersion = VersionOfFile(filename);
                            if (fileVersion > currentVersion && (newVersion == null || fileVersion < newVersion))
                                newVersion = fileVersion;
                        }
                    }
                    if(newVersion != null)
                    {
                        string scriptPath = FolderPath + "\\" + ScriptFileName + "." + newVersion + ScriptExtension;
                        m_Log.Info(string.Format("Start to update database. Update file : {0}", scriptPath));
                        DataProvider dataProvider = new DataProvider(ConnectionString);
                        SqlTransaction transaction = null;
                        try
                        {
                            string sqlCommandText = string.Empty;
                            using(StreamReader reader = new StreamReader(File.Open(scriptPath, FileMode.Open)))
                            {
                                sqlCommandText = reader.ReadToEnd();
                            }
                            transaction = dataProvider.BeginTransaction(IsolationLevel.Unspecified);
                            SqlCommand command = dataProvider.PrepareCommand(sqlCommandText, CommandType.Text, transaction);
                            dataProvider.ExecuteScalar(command);
                            dataProvider.CommitTransaction(transaction);
                            InvokeSetDatabaseVersion(newVersion);
                        }
                        catch(Exception ex)
                        {
                            m_Log.Error("Update of database is failed.", ex);    
                            if(transaction != null)
                                dataProvider.RollbackTransaction(transaction);
                        }
                        m_Log.Info(string.Format("Finish to update database. Update file : {0}", scriptPath));
                    }
                    else
                    {
                        m_Log.Info("New update scripts is not exists");
                    }
                }
                else
                {
                    m_Log.Error(string.Format("Directory with script files is not exists : {0}", FolderPath));
                }
            }
        }

        #endregion

        #region private methods

        /// <summary>
        /// Invoke <see cref="GetDatabaseVersion"/>.
        /// </summary>
        /// <returns>The current version of the database.</returns>
        private Version InvokeGetDatabaseVersion()
        {
            Version result = null;
            if (GetDatabaseVersion != null)
                result = GetDatabaseVersion();
            else
                m_Log.Warn("GetDatabaseVersion does not initialize");
            return result;
        }

        /// <summary>
        /// Invoke <see cref="SetDatabaseVersion"/>.
        /// </summary>
        /// <param name="version">The current version of the database.</param>
        private void InvokeSetDatabaseVersion(Version version)
        {
            if (SetDatabaseVersion != null)
                SetDatabaseVersion(version);
            else
                m_Log.Warn("SetDatabaseVersion does not initialize");
        }

        /// <summary>
        /// Extracts the version from a filename.
        /// </summary>
        /// <param name="filename">Name of file with version</param>
        /// <returns>Version of file</returns>
        private Version VersionOfFile(string filename)
        {
            Match match = Regex.Match(filename, @"\.(\d*\.\d*\.\d*\.\d*)\.");
            if (match.Success)
                return new Version(Regex.Match(filename, @"\.(\d*\.\d*\.\d*\.\d*)\.").Groups[1].Value);
            else
                return new Version(0, 0, 0, 0);
        }

        #endregion
    }
}
